home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / DB / Table.php < prev   
Encoding:
PHP Script  |  2007-12-20  |  75.9 KB  |  2,650 lines

  1. <?php
  2.  
  3. /**
  4. * DB_Table is a database API and data type SQL abstraction class.
  5. * DB_Table provides database API abstraction, data type abstraction,
  6. * automated SELECT, INSERT, and UPDATE queries, automated table
  7. * creation, automated validation of inserted/updated column values,
  8. * and automated creation of QuickForm elements based on the column
  9. * definitions.
  10. * @category DB
  11. * @package DB_Table
  12. *
  13. * @author Paul M. Jones <pmjones@php.net>
  14. * @author Mark Wiesemann <wiesemann@php.net>
  15. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  16. * @version $Id: Table.php,v 1.69 2006/11/09 20:56:30 wiesemann Exp $
  17. *
  18. */
  19.  
  20. /**
  21. * Error code at instantiation time when the first parameter to the
  22. * constructor is not a PEAR DB object.
  23. */
  24. define('DB_TABLE_ERR_NOT_DB_OBJECT',    -1);
  25.  
  26. /**
  27. * Error code at instantiation time when the PEAR DB/MDB2 $phptype is not
  28. * supported by DB_Table.
  29. */
  30. define('DB_TABLE_ERR_PHPTYPE',          -2);
  31.  
  32. /**
  33. * Error code when you call select() or selectResult() and the first
  34. * parameter does not match any of the $this->sql keys.
  35. */
  36. define('DB_TABLE_ERR_SQL_UNDEF',        -3);
  37.  
  38. /**
  39. * Error code when you try to insert data to a column that is not in the
  40. * $this->col array.
  41. */
  42. define('DB_TABLE_ERR_INS_COL_NOMAP',    -4);
  43.  
  44. /**
  45. * Error code when you try to insert data, and that data does not have a
  46. * column marked as 'require' in the $this->col array.
  47. */
  48. define('DB_TABLE_ERR_INS_COL_REQUIRED', -5);
  49.  
  50. /**
  51. * Error code when auto-validation fails on data to be inserted.
  52. */
  53. define('DB_TABLE_ERR_INS_DATA_INVALID', -6);
  54.  
  55. /**
  56. * Error code when you try to update data to a column that is not in the
  57. * $this->col array.
  58. */
  59. define('DB_TABLE_ERR_UPD_COL_NOMAP',    -7);
  60.  
  61. /**
  62. * Error code when you try to update data, and that data does not have a
  63. * column marked as 'require' in the $this->col array.
  64. */
  65. define('DB_TABLE_ERR_UPD_COL_REQUIRED', -8);
  66.  
  67. /**
  68. * Error code when auto-validation fails on update data.
  69. */
  70. define('DB_TABLE_ERR_UPD_DATA_INVALID', -9);
  71.  
  72. /**
  73. * Error code when you use a create() flag that is not recognized (must
  74. * be 'safe', 'drop', 'verify' or boolean false.
  75. */
  76. define('DB_TABLE_ERR_CREATE_FLAG',      -10);
  77.  
  78. /**
  79. * Error code at create() time when you define an index in $this->idx
  80. * that has no columns.
  81. */
  82. define('DB_TABLE_ERR_IDX_NO_COLS',      -11);
  83.  
  84. /**
  85. * Error code at create() time when you define an index in $this->idx
  86. * that refers to a column that does not exist in the $this->col array.
  87. */
  88. define('DB_TABLE_ERR_IDX_COL_UNDEF',    -12);
  89.  
  90. /**
  91. * Error code at create() time when you define a $this->idx index type
  92. * that is not recognized (must be 'normal' or 'unique').
  93. */
  94. define('DB_TABLE_ERR_IDX_TYPE',         -13);
  95.  
  96. /**
  97. * Error code at create() time when you have an error in a 'char' or
  98. * 'varchar' definition in $this->col (usually because 'size' is wrong).
  99. */
  100. define('DB_TABLE_ERR_DECLARE_STRING',   -14);
  101.  
  102. /**
  103. * Error code at create() time when you have an error in a 'decimal'
  104. * definition (usually becuase the 'size' or 'scope' are wrong).
  105. */
  106. define('DB_TABLE_ERR_DECLARE_DECIMAL',  -15);
  107.  
  108. /**
  109. * Error code at create() time when you define a column in $this->col
  110. * with an unrecognized 'type'.
  111. */
  112. define('DB_TABLE_ERR_DECLARE_TYPE',     -16);
  113.  
  114. /**
  115. * Error code at validation time when a column in $this->col has an
  116. * unrecognized 'type'.
  117. */
  118. define('DB_TABLE_ERR_VALIDATE_TYPE',    -17);
  119.  
  120. /**
  121. * Error code at create() time when you define a column in $this->col
  122. * with an invalid column name (usually because it's a reserved keyword).
  123. */
  124. define('DB_TABLE_ERR_DECLARE_COLNAME',  -18);
  125.  
  126. /**
  127. * Error code at create() time when you define an index in $this->idx
  128. * with an invalid index name (usually because it's a reserved keyword).
  129. */
  130. define('DB_TABLE_ERR_DECLARE_IDXNAME',  -19);
  131.  
  132. /**
  133. * Error code at create() time when you define an index in $this->idx
  134. * that refers to a CLOB column.
  135. */
  136. define('DB_TABLE_ERR_IDX_COL_CLOB',     -20);
  137.  
  138. /**
  139. * Error code at create() time when you define a column name that is
  140. * more than 30 chars long (an Oracle restriction).
  141. */
  142. define('DB_TABLE_ERR_DECLARE_STRLEN',   -21);
  143.  
  144. /**
  145. * Error code at create() time when the index name ends up being more
  146. * than 30 chars long (an Oracle restriction).
  147. */
  148. define('DB_TABLE_ERR_IDX_STRLEN',       -22);
  149.  
  150. /**
  151. * Error code at create() time when the table name is more than 30 chars
  152. * long (an Oracle restriction).
  153. */
  154. define('DB_TABLE_ERR_TABLE_STRLEN',     -23);
  155.  
  156. /**
  157. * Error code at nextID() time when the sequence name is more than 30
  158. * chars long (an Oracle restriction).
  159. */
  160. define('DB_TABLE_ERR_SEQ_STRLEN',       -24);
  161.  
  162. /**
  163. * Error code at verify() time when the table does not exist in the
  164. * database.
  165. */
  166. define('DB_TABLE_ERR_VER_TABLE_MISSING', -25);
  167.  
  168. /**
  169. * Error code at verify() time when the column does not exist in the
  170. * database table.
  171. */
  172. define('DB_TABLE_ERR_VER_COLUMN_MISSING', -26);
  173.  
  174. /**
  175. * Error code at verify() time when the column type does not match the
  176. * type specified in the column declaration.
  177. */
  178. define('DB_TABLE_ERR_VER_COLUMN_TYPE',  -27);
  179.  
  180. /**
  181. * Error code at instantiation time when the column definition array
  182. * does not contain at least one column.
  183. */
  184. define('DB_TABLE_ERR_NO_COLS',          -28);
  185.  
  186. /**
  187. * Error code at verify() time when an index cannot be found in the
  188. * database table.
  189. */
  190. define('DB_TABLE_ERR_VER_IDX_MISSING',   -29);
  191.  
  192. /**
  193. * Error code at verify() time when an index does not contain all
  194. * columns that it should contain.
  195. */
  196. define('DB_TABLE_ERR_VER_IDX_COL_MISSING', -30);
  197.  
  198. /**
  199. * Error code at instantiation time when a creation mode
  200. * is not available for a phptype.
  201. */
  202. define('DB_TABLE_ERR_CREATE_PHPTYPE', -31);
  203.  
  204. /**
  205. * Error code at create() time when you define more than one primary key
  206. * in $this->idx.
  207. */
  208. define('DB_TABLE_ERR_DECLARE_PRIMARY', -32);
  209.  
  210. /**
  211. * Error code at create() time when a primary key is defined in $this->idx
  212. * and SQLite is used (SQLite does not support primary keys).
  213. */
  214. define('DB_TABLE_ERR_DECLARE_PRIM_SQLITE', -33);
  215.  
  216. /**
  217. * Error code at alter() time when altering a table field is not possible
  218. * (e.g. because MDB2 has no support for the change or because the DBMS
  219. * does not support the change).
  220. */
  221. define('DB_TABLE_ERR_ALTER_TABLE_IMPOS', -34);
  222.  
  223. /**
  224. * Error code at alter() time when altering a(n) index/constraint is not possible
  225. * (e.g. because MDB2 has no support for the change or because the DBMS
  226. * does not support the change).
  227. */
  228. define('DB_TABLE_ERR_ALTER_INDEX_IMPOS', -35);
  229.  
  230. /**
  231. * The PEAR class for errors
  232. */
  233. require_once 'PEAR.php';
  234.  
  235. /**
  236. * The Date class for recasting date and time values
  237. */
  238. require_once 'DB/Table/Date.php';
  239.  
  240.  
  241. /**
  242. * DB_Table supports these RDBMS engines and their various native data
  243. * types; we need these here instead of in Manager.php because the
  244. * initial array key tells us what databases are supported.
  245. */
  246. $GLOBALS['_DB_TABLE']['type'] = array(
  247.     'fbsql' => array(
  248.         'boolean'   => 'DECIMAL(1,0)',
  249.         'char'      => 'CHAR',
  250.         'varchar'   => 'VARCHAR',
  251.         'smallint'  => 'SMALLINT',
  252.         'integer'   => 'INTEGER',
  253.         'bigint'    => 'LONGINT',
  254.         'decimal'   => 'DECIMAL',
  255.         'single'    => 'REAL',
  256.         'double'    => 'DOUBLE PRECISION',
  257.         'clob'      => 'CLOB',
  258.         'date'      => 'CHAR(10)',
  259.         'time'      => 'CHAR(8)',
  260.         'timestamp' => 'CHAR(19)'
  261.     ),
  262.     'ibase' => array(
  263.         'boolean'   => 'DECIMAL(1,0)',
  264.         'char'      => 'CHAR',
  265.         'varchar'   => 'VARCHAR',
  266.         'smallint'  => 'SMALLINT',
  267.         'integer'   => 'INTEGER',
  268.         'bigint'    => 'BIGINT',
  269.         'decimal'   => 'DECIMAL',
  270.         'single'    => 'FLOAT',
  271.         'double'    => 'DOUBLE PRECISION',
  272.         'clob'      => 'BLOB SUB_TYPE 1',
  273.         'date'      => 'DATE',
  274.         'time'      => 'TIME',
  275.         'timestamp' => 'TIMESTAMP'
  276.     ),
  277.     'mssql' => array(
  278.         'boolean'   => 'DECIMAL(1,0)',
  279.         'char'      => 'CHAR',
  280.         'varchar'   => 'VARCHAR',
  281.         'smallint'  => 'SMALLINT',
  282.         'integer'   => 'INTEGER',
  283.         'bigint'    => 'BIGINT',
  284.         'decimal'   => 'DECIMAL',
  285.         'single'    => 'REAL',
  286.         'double'    => 'FLOAT',
  287.         'clob'      => 'TEXT',
  288.         'date'      => 'CHAR(10)',
  289.         'time'      => 'CHAR(8)',
  290.         'timestamp' => 'CHAR(19)'
  291.     ),
  292.     'mysql' => array(
  293.         'boolean'   => 'DECIMAL(1,0)',
  294.         'char'      => 'CHAR',
  295.         'varchar'   => 'VARCHAR',
  296.         'smallint'  => 'SMALLINT',
  297.         'integer'   => 'INTEGER',
  298.         'bigint'    => 'BIGINT',
  299.         'decimal'   => 'DECIMAL',
  300.         'single'    => 'FLOAT',
  301.         'double'    => 'DOUBLE',
  302.         'clob'      => 'LONGTEXT',
  303.         'date'      => 'CHAR(10)',
  304.         'time'      => 'CHAR(8)',
  305.         'timestamp' => 'CHAR(19)'
  306.     ),
  307.     'mysqli' => array(
  308.         'boolean'   => 'DECIMAL(1,0)',
  309.         'char'      => 'CHAR',
  310.         'varchar'   => 'VARCHAR',
  311.         'smallint'  => 'SMALLINT',
  312.         'integer'   => 'INTEGER',
  313.         'bigint'    => 'BIGINT',
  314.         'decimal'   => 'DECIMAL',
  315.         'single'    => 'FLOAT',
  316.         'double'    => 'DOUBLE',
  317.         'clob'      => 'LONGTEXT',
  318.         'date'      => 'CHAR(10)',
  319.         'time'      => 'CHAR(8)',
  320.         'timestamp' => 'CHAR(19)'
  321.     ),
  322.     'oci8' => array(
  323.         'boolean'   => 'NUMBER(1)',
  324.         'char'      => 'CHAR',
  325.         'varchar'   => 'VARCHAR2',
  326.         'smallint'  => 'NUMBER(6)',
  327.         'integer'   => 'NUMBER(11)',
  328.         'bigint'    => 'NUMBER(19)',
  329.         'decimal'   => 'NUMBER',
  330.         'single'    => 'REAL',
  331.         'double'    => 'DOUBLE PRECISION',
  332.         'clob'      => 'CLOB',
  333.         'date'      => 'CHAR(10)',
  334.         'time'      => 'CHAR(8)',
  335.         'timestamp' => 'CHAR(19)'
  336.     ),
  337.     'pgsql' => array(
  338.         'boolean'   => 'DECIMAL(1,0)',
  339.         'char'      => 'CHAR',
  340.         'varchar'   => 'VARCHAR',
  341.         'smallint'  => 'SMALLINT',
  342.         'integer'   => 'INTEGER',
  343.         'bigint'    => 'BIGINT',
  344.         'decimal'   => 'DECIMAL',
  345.         'single'    => 'REAL',
  346.         'double'    => 'DOUBLE PRECISION',
  347.         'clob'      => 'TEXT',
  348.         'date'      => 'CHAR(10)',
  349.         'time'      => 'CHAR(8)',
  350.         'timestamp' => 'CHAR(19)'
  351.     ),
  352.     'sqlite' => array(
  353.         'boolean'   => 'BOOLEAN',
  354.         'char'      => 'CHAR',
  355.         'varchar'   => 'VARCHAR',
  356.         'smallint'  => 'SMALLINT',
  357.         'integer'   => 'INTEGER',
  358.         'bigint'    => 'BIGINT',
  359.         'decimal'   => 'NUMERIC',
  360.         'single'    => 'FLOAT',
  361.         'double'    => 'DOUBLE',
  362.         'clob'      => 'CLOB',
  363.         'date'      => 'DATE',
  364.         'time'      => 'TIME',
  365.         'timestamp' => 'TIMESTAMP'
  366.     )
  367. );
  368.  
  369.  
  370. /** 
  371.   * US-English default error messages. If you want to internationalize, you can
  372.   * set the translated messages via $GLOBALS['_DB_TABLE']['error']. You can also
  373.   * use DB_Table::setErrorMessage(). Examples:
  374.   * 
  375.   * <code>
  376.   * (1) $GLOBALS['_DB_TABLE]['error'] = array(DB_TABLE_ERR_PHPTYPE   => '...',
  377.   *                                           DB_TABLE_ERR_SQL_UNDEF => '...');
  378.   * (2) DB_Table::setErrorMessage(DB_TABLE_ERR_PHPTYPE,   '...');
  379.   *     DB_Table::setErrorMessage(DB_TABLE_ERR_SQL_UNDEF, '...');
  380.   * (3) DB_Table::setErrorMessage(array(DB_TABLE_ERR_PHPTYPE   => '...');
  381.   *                                     DB_TABLE_ERR_SQL_UNDEF => '...');
  382.   * (4) $obj =& new DB_Table();
  383.   *     $obj->setErrorMessage(DB_TABLE_ERR_PHPTYPE,   '...');
  384.   *     $obj->setErrorMessage(DB_TABLE_ERR_SQL_UNDEF, '...');
  385.   * (5) $obj =& new DB_Table();
  386.   *     $obj->setErrorMessage(array(DB_TABLE_ERR_PHPTYPE   => '...');
  387.   *                                 DB_TABLE_ERR_SQL_UNDEF => '...');
  388.   * </code>
  389.   * 
  390.   * For errors that can occur with-in the constructor call (i.e. e.g. creating
  391.   * or altering the database table), only the code from examples (1) to (3)
  392.   * will alter the default error messages early enough. For errors that can
  393.   * occur later, examples (4) and (5) are also valid.
  394.   */
  395. $GLOBALS['_DB_TABLE']['default_error'] = array(
  396.     DB_TABLE_ERR_NOT_DB_OBJECT       => 'First parameter must be a DB/MDB2 object',
  397.     DB_TABLE_ERR_PHPTYPE             => 'DB/MDB2 phptype (or dbsyntax) not supported',
  398.     DB_TABLE_ERR_SQL_UNDEF           => 'Select key not in map',
  399.     DB_TABLE_ERR_INS_COL_NOMAP       => 'Insert column not in map',
  400.     DB_TABLE_ERR_INS_COL_REQUIRED    => 'Insert data must be set and non-null for column',
  401.     DB_TABLE_ERR_INS_DATA_INVALID    => 'Insert data not valid for column',
  402.     DB_TABLE_ERR_UPD_COL_NOMAP       => 'Update column not in map',
  403.     DB_TABLE_ERR_UPD_COL_REQUIRED    => 'Update column must be set and non-null',
  404.     DB_TABLE_ERR_UPD_DATA_INVALID    => 'Update data not valid for column',
  405.     DB_TABLE_ERR_CREATE_FLAG         => 'Create flag not valid',
  406.     DB_TABLE_ERR_IDX_NO_COLS         => 'No columns for index',
  407.     DB_TABLE_ERR_IDX_COL_UNDEF       => 'Column not in map for index',
  408.     DB_TABLE_ERR_IDX_TYPE            => 'Type not valid for index',
  409.     DB_TABLE_ERR_DECLARE_STRING      => 'String column declaration not valid',
  410.     DB_TABLE_ERR_DECLARE_DECIMAL     => 'Decimal column declaration not valid',
  411.     DB_TABLE_ERR_DECLARE_TYPE        => 'Column type not valid',
  412.     DB_TABLE_ERR_VALIDATE_TYPE       => 'Cannot validate for unknown type on column',
  413.     DB_TABLE_ERR_DECLARE_COLNAME     => 'Column name not valid',
  414.     DB_TABLE_ERR_DECLARE_IDXNAME     => 'Index name not valid',
  415.     DB_TABLE_ERR_DECLARE_TYPE        => 'Column type not valid',
  416.     DB_TABLE_ERR_IDX_COL_CLOB        => 'CLOB column not allowed for index',
  417.     DB_TABLE_ERR_DECLARE_STRLEN      => 'Column name too long, 30 char max',
  418.     DB_TABLE_ERR_IDX_STRLEN          => 'Index name too long, 30 char max',
  419.     DB_TABLE_ERR_TABLE_STRLEN        => 'Table name too long, 30 char max',
  420.     DB_TABLE_ERR_SEQ_STRLEN          => 'Sequence name too long, 30 char max',
  421.     DB_TABLE_ERR_VER_TABLE_MISSING   => 'Verification failed: table does not exist',
  422.     DB_TABLE_ERR_VER_COLUMN_MISSING  => 'Verification failed: column does not exist',
  423.     DB_TABLE_ERR_VER_COLUMN_TYPE     => 'Verification failed: wrong column type',
  424.     DB_TABLE_ERR_NO_COLS             => 'Column definition array may not be empty',
  425.     DB_TABLE_ERR_VER_IDX_MISSING     => 'Verification failed: index does not exist',
  426.     DB_TABLE_ERR_VER_IDX_COL_MISSING => 'Verification failed: index does not contain all specified cols',
  427.     DB_TABLE_ERR_CREATE_PHPTYPE      => 'Creation mode is not supported for this phptype',
  428.     DB_TABLE_ERR_DECLARE_PRIMARY     => 'Only one primary key is allowed',
  429.     DB_TABLE_ERR_DECLARE_PRIM_SQLITE => 'SQLite does not support primary keys',
  430.     DB_TABLE_ERR_ALTER_TABLE_IMPOS   => 'Alter table failed: changing the field type not possible',
  431.     DB_TABLE_ERR_ALTER_INDEX_IMPOS   => 'Alter table failed: changing the index/constraint not possible'
  432. );
  433.  
  434. // merge default and user-defined error messages
  435. if (!isset($GLOBALS['_DB_TABLE']['error'])) {
  436.     $GLOBALS['_DB_TABLE']['error'] = array();
  437. }
  438. foreach ($GLOBALS['_DB_TABLE']['default_error'] as $code => $message) {
  439.     if (!array_key_exists($code, $GLOBALS['_DB_TABLE']['error'])) {
  440.         $GLOBALS['_DB_TABLE']['error'][$code] = $message;
  441.     }
  442. }
  443.  
  444. /**
  445. * DB_Table is a database API and data type SQL abstraction class.
  446. * DB_Table provides database API abstraction, data type abstraction,
  447. * automated SELECT, INSERT, and UPDATE queries, automated table
  448. * creation, automated validation of inserted/updated column values,
  449. * and automated creation of QuickForm elemnts based on the column
  450. * definitions.
  451. * @category DB
  452. * @package DB_Table
  453. * @author Paul M. Jones <pmjones@php.net>
  454. * @author Mark Wiesemann <wiesemann@php.net>
  455. * @version 1.4.0
  456. *
  457. */
  458.  
  459. class DB_Table {
  460.     
  461.     
  462.     /**
  463.     * 
  464.     * The PEAR DB/MDB2 object that connects to the database.
  465.     * 
  466.     * @access public
  467.     * 
  468.     * @var object
  469.     * 
  470.     */
  471.     
  472.     var $db = null;
  473.     
  474.     
  475.     /**
  476.     * 
  477.     * The backend type
  478.     * 
  479.     * @access public
  480.     * 
  481.     * @var string
  482.     * 
  483.     */
  484.     
  485.     var $backend = null;
  486.     
  487.     
  488.     /**
  489.     * 
  490.     * The table or view in the database to which this object binds.
  491.     * 
  492.     * @access public
  493.     * 
  494.     * @var string
  495.     * 
  496.     */
  497.     
  498.     var $table = null;
  499.     
  500.     
  501.     /**
  502.     * 
  503.     * Associative array of column definitions.
  504.     * 
  505.     * @access public
  506.     * 
  507.     * @var array
  508.     * 
  509.     */
  510.     
  511.     var $col = array();
  512.     
  513.     
  514.     /**
  515.     * 
  516.     * Associative array of index definitions.
  517.     * 
  518.     * @access public
  519.     * 
  520.     * @var array
  521.     * 
  522.     */
  523.     
  524.     var $idx = array();
  525.     
  526.     
  527.     /**
  528.     * 
  529.     * Baseline SELECT maps for select(), selectResult(), selectCount().
  530.     * 
  531.     * @access public
  532.     * 
  533.     * @var array
  534.     * 
  535.     */
  536.     
  537.     var $sql = array();
  538.     
  539.     
  540.     /**
  541.     * 
  542.     * Whether or not to automatically validate data at insert-time.
  543.     * 
  544.     * @access private
  545.     * 
  546.     * @var bool
  547.     * 
  548.     */
  549.     
  550.     var $_valid_insert = true;
  551.     
  552.     
  553.     /**
  554.     * 
  555.     * Whether or not to automatically validate data at update-time.
  556.     * 
  557.     * @access private
  558.     * 
  559.     * @var bool
  560.     * 
  561.     */
  562.     
  563.     var $_valid_update = true;
  564.     
  565.     
  566.     /**
  567.     * 
  568.     * When calling select() and selectResult(), use this fetch mode (usually
  569.     * a DB_FETCHMODE_* constant).  If null, uses whatever is set in the $db
  570.     * PEAR DB/MDB2 object.
  571.     * 
  572.     * @access public
  573.     * 
  574.     * @var int
  575.     * 
  576.     */
  577.     
  578.     var $fetchmode = null;
  579.     
  580.     
  581.     /**
  582.     * 
  583.     * When fetchmode is DB_FETCHMODE_OBJECT, use this class for each
  584.     * returned row.  If null, uses whatever is set in the $db
  585.     * PEAR DB/MDB2 object.
  586.     * 
  587.     * @access public
  588.     * 
  589.     * @var string
  590.     * 
  591.     */
  592.     
  593.     var $fetchmode_object_class = null;
  594.     
  595.     
  596.     /**
  597.     * 
  598.     * If there is an error on instantiation, this captures that error.
  599.     *
  600.     * This property is used only for errors encountered in the constructor
  601.     * at instantiation time.  To check if there was an instantiation error...
  602.     *
  603.     * <code>
  604.     * $obj =& new DB_Table();
  605.     * if ($obj->error) {
  606.     *     // ... error handling code here ...
  607.     * }
  608.     * </code>
  609.     * 
  610.     * @var object PEAR_Error
  611.     * 
  612.     */
  613.     
  614.     var $error = null;
  615.  
  616.  
  617.     /**
  618.     * 
  619.     * Whether or not to automatically recast data at insert- and update-time.
  620.     * 
  621.     * @access private
  622.     * 
  623.     * @var bool
  624.     * 
  625.     */
  626.     
  627.     var $_auto_recast = true;
  628.     
  629.     
  630.     /**
  631.     * 
  632.     * Specialized version of throwError() modeled on PEAR_Error.
  633.     * 
  634.     * Throws a PEAR_Error with a DB_Table error message based on a
  635.     * DB_Table constant error code.
  636.     * 
  637.     * @static
  638.     * 
  639.     * @access public
  640.     * 
  641.     * @param string $code A DB_Table error code constant.
  642.     * 
  643.     * @param string $extra Extra text for the error (in addition to the 
  644.     * regular error message).
  645.     * 
  646.     * @return object PEAR_Error
  647.     * 
  648.     */
  649.     
  650.     function &throwError($code, $extra = null)
  651.     {
  652.         // get the error message text based on the error code
  653.         $text = $GLOBALS['_DB_TABLE']['error'][$code];
  654.         
  655.         // add any additional error text
  656.         if ($extra) {
  657.             $text .= ' ' . $extra;
  658.         }
  659.         
  660.         // done!
  661.         $error = PEAR::throwError($text, $code);
  662.         return $error;
  663.     }
  664.     
  665.     
  666.     /**
  667.     * 
  668.     * Constructor.
  669.     * 
  670.     * If there is an error on instantiation, $this->error will be 
  671.     * populated with the PEAR_Error.
  672.     * 
  673.     * @access public
  674.     * 
  675.     * @param object &$db A PEAR DB/MDB2 object.
  676.     * 
  677.     * @param string $table The table name to connect to in the database.
  678.     * 
  679.     * @param mixed $create The automatic table creation mode to pursue:
  680.     * - boolean false to not attempt creation
  681.     * - 'safe' to create the table only if it does not exist
  682.     * - 'drop' to drop any existing table with the same name and re-create it
  683.     * - 'verify' to check whether the table exists, whether all the columns
  684.     *   exist, whether the columns have the right type, and whether the indexes
  685.     *   exist and have the right type
  686.     * - 'alter' does the same as 'safe' if the table does not exist; if it
  687.     *   exists, a verification for columns existence, the column types, the
  688.     *   indexes existence, and the indexes types will be performed and the
  689.     *   table schema will be modified if needed
  690.     * 
  691.     * @return object DB_Table
  692.     * 
  693.     */
  694.     
  695.     function DB_Table(&$db, $table, $create = false)
  696.     {
  697.         // is the first argument a DB/MDB2 object?
  698.         $this->backend = null;
  699.         if (is_subclass_of($db, 'db_common')) {
  700.             $this->backend = 'db';
  701.         } elseif (is_subclass_of($db, 'mdb2_driver_common')) {
  702.             $this->backend = 'mdb2';
  703.         }
  704.  
  705.         if (is_null($this->backend)) {
  706.             $this->error =& DB_Table::throwError(DB_TABLE_ERR_NOT_DB_OBJECT);
  707.             return;
  708.         }
  709.         
  710.         // array with column definition may not be empty        
  711.         if (! isset($this->col) || is_null($this->col) ||
  712.                 (is_array($this->col) && count($this->col) === 0)) {
  713.             $this->error =& DB_Table::throwError(DB_TABLE_ERR_NO_COLS);
  714.             return;
  715.         }
  716.  
  717.         // set the class properties
  718.         $this->db =& $db;
  719.         $this->table = $table;
  720.         
  721.         // is the RDBMS supported?
  722.         list($phptype, $dbsyntax) = DB_Table::getPHPTypeAndDBSyntax($db);
  723.         if (! DB_Table::supported($phptype, $dbsyntax)) {
  724.             $this->error =& DB_Table::throwError(
  725.                 DB_TABLE_ERR_PHPTYPE,
  726.                 "({$db->phptype})"
  727.             );
  728.             return;
  729.         }
  730.  
  731.         // load MDB2_Extended module
  732.         if ($this->backend == 'mdb2') {
  733.             $this->db->loadModule('Extended', null, false);
  734.         }
  735.  
  736.         // should we attempt table creation?
  737.         if ($create) {
  738.  
  739.             if ($this->backend == 'mdb2') {
  740.                 $this->db->loadModule('Manager');
  741.             }
  742.  
  743.             // check whether the chosen mode is supported
  744.             list($phptype,) = DB_Table::getPHPTypeAndDBSyntax($this->db);
  745.             $mode_supported = DB_Table::modeSupported($create, $phptype);
  746.             if (PEAR::isError($mode_supported)) {
  747.                 $this->error =& $mode_supported;
  748.                 return;
  749.             }
  750.             if (!$mode_supported) {
  751.                 $this->error =& $this->throwError(
  752.                     DB_TABLE_ERR_CREATE_PHPTYPE,
  753.                     "('$create', '$phptype')"
  754.                 );
  755.                 return;
  756.             }
  757.  
  758.             include_once 'DB/Table/Manager.php';
  759.  
  760.             switch ($create) {
  761.  
  762.                 case 'alter':
  763.                     $result = $this->alter();
  764.                     break;
  765.  
  766.                 case 'drop':
  767.                 case 'safe':
  768.                     $result = $this->create($create);
  769.                     break;
  770.  
  771.                 case 'verify':
  772.                     $result = $this->verify();
  773.                     break;
  774.             }
  775.             
  776.             if (PEAR::isError($result)) {
  777.                 // problem creating/altering/verifing the table
  778.                 $this->error =& $result;
  779.                 return;
  780.             }
  781.         }
  782.     }
  783.     
  784.     
  785.     /**
  786.     * 
  787.     * Is a particular RDBMS supported by DB_Table?
  788.     * 
  789.     * @static
  790.     * 
  791.     * @access public
  792.     * 
  793.     * @param string $phptype The RDBMS type for PHP.
  794.     * 
  795.     * @param string $dbsyntax The chosen database syntax.
  796.     * 
  797.     * @return bool True if supported, false if not.
  798.     * 
  799.     */
  800.     
  801.     function supported($phptype, $dbsyntax = '')
  802.     {
  803.         // only Firebird is supported, not its ancestor Interbase
  804.         if ($phptype == 'ibase' && $dbsyntax != 'firebird') {
  805.             return false;
  806.         }
  807.         $supported = array_keys($GLOBALS['_DB_TABLE']['type']);
  808.         return in_array(strtolower($phptype), $supported);
  809.     }
  810.  
  811.  
  812.     /**
  813.     * 
  814.     * Is a creation mode supported for a RDBMS by DB_Table?
  815.     * 
  816.     * @access public
  817.     * 
  818.     * @param string $mode The chosen creation mode.
  819.     * 
  820.     * @param string $phptype The RDBMS type for PHP.
  821.     * 
  822.     * @return bool|object True if supported, false if not, or a PEAR_Error
  823.     * if an unknown mode is specified.
  824.     * 
  825.     */
  826.     
  827.     function modeSupported($mode, $phptype)
  828.     {
  829.         // check phptype for validity
  830.         $supported = array_keys($GLOBALS['_DB_TABLE']['type']);
  831.         if (!in_array(strtolower($phptype), $supported)) {
  832.             return false;
  833.         }
  834.  
  835.         switch ($mode) {
  836.             case 'drop':
  837.             case 'safe':
  838.                 // supported for all RDBMS
  839.                 return true;
  840.  
  841.             case 'alter':
  842.             case 'verify':
  843.                 // not supported for fbsql and mssql (yet)
  844.                 switch ($phptype) {
  845.                     case 'fbsql':
  846.                     case 'mssql':
  847.                         return false;
  848.                     default:
  849.                         return true;
  850.                 }
  851.  
  852.             default:
  853.                 // unknown creation mode
  854.                 return $this->throwError(
  855.                     DB_TABLE_ERR_CREATE_FLAG,
  856.                     "('$mode')"
  857.                 );
  858.         }
  859.     }
  860.  
  861.  
  862.     /**
  863.     * 
  864.     * Detect values of 'phptype' and 'dbsyntax' keys of DSN.
  865.     * 
  866.     * @static
  867.     * 
  868.     * @access public
  869.     * 
  870.     * @param object &$db A PEAR DB/MDB2 object.
  871.     * 
  872.     * @return array Values of 'phptype' and 'dbsyntax' keys of DSN.
  873.     * 
  874.     */
  875.  
  876.     function getPHPTypeAndDBSyntax(&$db) {
  877.         $phptype = '';
  878.         $dbsyntax = '';
  879.         if (is_subclass_of($db, 'db_common')) {
  880.             $phptype = $db->phptype;
  881.             $dbsyntax = $db->dbsyntax;
  882.         } elseif (is_subclass_of($db, 'mdb2_driver_common')) {
  883.             $dsn = MDB2::parseDSN($db->getDSN());
  884.             $phptype = $dsn['phptype'];
  885.             $dbsyntax = $dsn['dbsyntax'];
  886.         }
  887.         return array($phptype, $dbsyntax);
  888.     }
  889.  
  890.  
  891.     /**
  892.     * 
  893.     * Overwrite one or more error messages, e.g. to internationalize them.
  894.     * 
  895.     * @access public
  896.     * 
  897.     * @param mixed $code If string, the error message with code $code will
  898.     * be overwritten by $message. If array, the error messages with code
  899.     * of each array key will be overwritten by the key's value.
  900.     * 
  901.     * @param string $message Only used if $key is not an array.
  902.     *
  903.     * @return void
  904.     * 
  905.     */
  906.  
  907.     function setErrorMessage($code, $message = null) {
  908.         if (is_array($code)) {
  909.             foreach ($code as $single_code => $single_message) {
  910.                 $GLOBALS['_DB_TABLE']['error'][$single_code] = $single_message;
  911.             }
  912.         } else {
  913.             $GLOBALS['_DB_TABLE']['error'][$code] = $message;
  914.         }
  915.     }
  916.  
  917.  
  918.     /**
  919.     * 
  920.     * Returns all or part of the $this->col property array.
  921.     * 
  922.     * @access public
  923.     * 
  924.     * @param mixed $col If null, returns the $this->col property array
  925.     * as it is.  If string, returns that column name from the $this->col
  926.     * array. If an array, returns those columns named as the array
  927.     * values from the $this->col array as an array.
  928.     *
  929.     * @return mixed All or part of the $this->col property array, or
  930.     * boolean false if no matching column names are found.
  931.     * 
  932.     */
  933.     
  934.     function getColumns($col = null)
  935.     {
  936.         // by default, return all column definitions
  937.         if (is_null($col)) {
  938.             return $this->col;
  939.         }
  940.         
  941.         // if the param is a string, only return the column definition
  942.         // named by the that string
  943.         if (is_string($col)) {
  944.             if (isset($this->col[$col])) {
  945.                 return $this->col[$col];
  946.             } else {
  947.                 return false;
  948.             }
  949.         }
  950.         
  951.         // if the param is a sequential array of column names,
  952.         // return only those columns named in that array
  953.         if (is_array($col)) {
  954.             $set = array();
  955.             foreach ($col as $name) {
  956.                 $set[$name] = $this->getColumns($name);
  957.             }
  958.             
  959.             if (count($set) == 0) {
  960.                 return false;
  961.             } else {
  962.                 return $set;
  963.             }
  964.         }
  965.         
  966.         // param was not null, string, or array
  967.         return false;
  968.     }
  969.     
  970.     
  971.     /**
  972.     * 
  973.     * Returns all or part of the $this->idx property array.
  974.     * 
  975.     * @access public
  976.     * 
  977.     * @param string $col If specified, returns only this index key
  978.     * from the $this->col property array.
  979.     * 
  980.     * @return array All or part of the $this->idx property array.
  981.     * 
  982.     */
  983.     
  984.     function getIndexes($idx = null)
  985.     {
  986.         // by default, return all index definitions
  987.         if (is_null($idx)) {
  988.             return $this->idx;
  989.         }
  990.         
  991.         // if the param is a string, only return the index definition
  992.         // named by the that string
  993.         if (is_string($idx)) {
  994.             if (isset($this->idx[$idx])) {
  995.                 return $this->idx[$idx];
  996.             } else {
  997.                 return false;
  998.             }
  999.         }
  1000.         
  1001.         // if the param is a sequential array of index names,
  1002.         // return only those indexes named in that array
  1003.         if (is_array($idx)) {
  1004.             $set = array();
  1005.             foreach ($idx as $name) {
  1006.                 $set[$name] = $this->getIndexes($name);
  1007.             }
  1008.             
  1009.             if (count($set) == 0) {
  1010.                 return false;
  1011.             } else {
  1012.                 return $set;
  1013.             }
  1014.         }
  1015.         
  1016.         // param was not null, string, or array
  1017.         return false;
  1018.     }
  1019.     
  1020.     
  1021.     /**
  1022.     *
  1023.     * Selects rows from the table using one of the DB/MDB2 get*() methods.
  1024.     * 
  1025.     * @access public
  1026.     * 
  1027.     * @param string $sqlkey The name of the SQL SELECT to use from the
  1028.     * $this->sql property array.
  1029.     * 
  1030.     * @param string $filter Ad-hoc SQL snippet to AND with the default
  1031.     * SELECT WHERE clause.
  1032.     * 
  1033.     * @param string $order Ad-hoc SQL snippet to override the default
  1034.     * SELECT ORDER BY clause.
  1035.     * 
  1036.     * @param int $start The row number to start listing from in the
  1037.     * result set.
  1038.     * 
  1039.     * @param int $count The number of rows to list in the result set.
  1040.     *
  1041.     * @param array $params Parameters to use in placeholder substitutions (if
  1042.     * any).
  1043.     * 
  1044.     * @return mixed An array of records from the table (if anything but
  1045.     * 'getOne'), a single value (if 'getOne'), or a PEAR_Error object.
  1046.     *
  1047.     * @see DB::getAll()
  1048.     * 
  1049.     * @see MDB2::getAll()
  1050.     *
  1051.     * @see DB::getAssoc()
  1052.     * 
  1053.     * @see MDB2::getAssoc()
  1054.     *
  1055.     * @see DB::getCol()
  1056.     * 
  1057.     * @see MDB2::getCol()
  1058.     *
  1059.     * @see DB::getOne()
  1060.     *
  1061.     * @see MDB2::getOne()
  1062.     * 
  1063.     * @see DB::getRow()
  1064.     * 
  1065.     * @see MDB2::getRow()
  1066.     *
  1067.     * @see DB_Table::_swapModes()
  1068.     *
  1069.     */
  1070.     
  1071.     function select($sqlkey, $filter = null, $order = null,
  1072.         $start = null, $count = null, $params = array())
  1073.     {
  1074.         // build the base command
  1075.         $sql = $this->buildSQL($sqlkey, $filter, $order, $start, $count);
  1076.         if (PEAR::isError($sql)) {
  1077.             return $sql;
  1078.         }
  1079.         
  1080.         // set the get*() method name
  1081.         if (isset($this->sql[$sqlkey]['get'])) {
  1082.             $method = ucwords(strtolower(trim($this->sql[$sqlkey]['get'])));
  1083.             $method = "get$method";
  1084.         } else {
  1085.             $method = 'getAll';
  1086.         }
  1087.         
  1088.         // DB_Table assumes you are using a shared PEAR DB/MDB2 object. Other
  1089.         // scripts using the same object probably expect its fetchmode
  1090.         // not to change, unless they change it themselves.  Thus, to
  1091.         // provide friendly mode-swapping, we will restore these modes
  1092.         // afterwards.
  1093.         $restore_mode = $this->db->fetchmode;
  1094.         if ($this->backend == 'mdb2') {
  1095.             $restore_class = $this->db->getOption('fetch_class');
  1096.         } else {
  1097.             $restore_class = $this->db->fetchmode_object_class;
  1098.         }
  1099.         // swap modes
  1100.         $fetchmode = $this->fetchmode;
  1101.         $fetchmode_object_class = $this->fetchmode_object_class;
  1102.         if (isset($this->sql[$sqlkey]['fetchmode'])) {
  1103.             $fetchmode = $this->sql[$sqlkey]['fetchmode'];
  1104.         }
  1105.         if (isset($this->sql[$sqlkey]['fetchmode_object_class'])) {
  1106.             $fetchmode_object_class = $this->sql[$sqlkey]['fetchmode_object_class'];
  1107.         }
  1108.         $this->_swapModes($fetchmode, $fetchmode_object_class);
  1109.  
  1110.         // make sure params is an array
  1111.         if (! is_null($params)) {
  1112.             $params = (array) $params;
  1113.         }
  1114.         
  1115.         // get the result
  1116.         if ($this->backend == 'mdb2') {
  1117.             $result = $this->db->extended->$method($sql, null, $params);
  1118.         } else {
  1119.             switch ($method) {
  1120.  
  1121.                 case 'getCol':
  1122.                     $result = $this->db->$method($sql, 0, $params);
  1123.                     break;
  1124.  
  1125.                 case 'getAssoc':
  1126.                     $result = $this->db->$method($sql, false, $params);
  1127.                     break;
  1128.  
  1129.                 default:
  1130.                     $result = $this->db->$method($sql, $params);
  1131.                     break;
  1132.  
  1133.             }
  1134.         }
  1135.             
  1136.         // swap modes back
  1137.         $this->_swapModes($restore_mode, $restore_class);
  1138.         
  1139.         // return the result
  1140.         return $result;
  1141.     }
  1142.     
  1143.     
  1144.     /**
  1145.     *
  1146.     * Selects rows from the table as a DB_Result/MDB2_Result_* object.
  1147.     * 
  1148.     * @access public
  1149.     * 
  1150.     * @param string $sqlkey The name of the SQL SELECT to use from the
  1151.     * $this->sql property array.
  1152.     * 
  1153.     * @param string $filter Ad-hoc SQL snippet to add to the default
  1154.     * SELECT WHERE clause.
  1155.     * 
  1156.     * @param string $order Ad-hoc SQL snippet to override the default
  1157.     * SELECT ORDER BY clause.
  1158.     * 
  1159.     * @param int $start The record number to start listing from in the
  1160.     * result set.
  1161.     * 
  1162.     * @param int $count The number of records to list in the result set.
  1163.     * 
  1164.     * @param array $params Parameters to use in placeholder substitutions (if
  1165.     * any).
  1166.     * 
  1167.     * @return mixed A PEAR_Error on failure, or a DB_Result/MDB2_Result_*
  1168.     * object on success.
  1169.     *
  1170.     * @see DB_Table::_swapModes()
  1171.     *
  1172.     */
  1173.     
  1174.     function selectResult($sqlkey, $filter = null, $order = null, 
  1175.         $start = null, $count = null, $params = array())
  1176.     {
  1177.         // build the base command
  1178.         $sql = $this->buildSQL($sqlkey, $filter, $order, $start, $count);
  1179.         if (PEAR::isError($sql)) {
  1180.             return $sql;
  1181.         }
  1182.         
  1183.         // DB_Table assumes you are using a shared PEAR DB/MDB2 object.  Other
  1184.         // scripts using the same object probably expect its fetchmode
  1185.         // not to change, unless they change it themselves.  Thus, to
  1186.         // provide friendly mode-swapping, we will restore these modes
  1187.         // afterwards.
  1188.         $restore_mode = $this->db->fetchmode;
  1189.         if ($this->backend == 'mdb2') {
  1190.             $restore_class = $this->db->getOption('fetch_class');
  1191.         } else {
  1192.             $restore_class = $this->db->fetchmode_object_class;
  1193.         }
  1194.         
  1195.         // swap modes
  1196.         $fetchmode = $this->fetchmode;
  1197.         $fetchmode_object_class = $this->fetchmode_object_class;
  1198.         if (isset($this->sql[$sqlkey]['fetchmode'])) {
  1199.             $fetchmode = $this->sql[$sqlkey]['fetchmode'];
  1200.         }
  1201.         if (isset($this->sql[$sqlkey]['fetchmode_object_class'])) {
  1202.             $fetchmode_object_class = $this->sql[$sqlkey]['fetchmode_object_class'];
  1203.         }
  1204.         $this->_swapModes($fetchmode, $fetchmode_object_class);
  1205.         
  1206.         // make sure params is an array
  1207.         if (! is_null($params)) {
  1208.             $params = (array) $params;
  1209.         }
  1210.      
  1211.         // get the result
  1212.         if ($this->backend == 'mdb2') {
  1213.             $stmt =& $this->db->prepare($sql);
  1214.             $result =& $stmt->execute($params);
  1215.         } else {
  1216.             $result =& $this->db->query($sql, $params);
  1217.         }
  1218.         
  1219.         // swap modes back
  1220.         $this->_swapModes($restore_mode, $restore_class);
  1221.         
  1222.         // return the result
  1223.         return $result;
  1224.     }
  1225.     
  1226.     
  1227.     /**
  1228.     *
  1229.     * Counts the number of rows which will be returned by a query.
  1230.     *
  1231.     * This function works identically to {@link select()}, but it
  1232.     * returns the number of rows returned by a query instead of the
  1233.     * query results themselves.
  1234.     *
  1235.     * This makes using DB_Table with Pager easier, since you can pass the
  1236.     * return value of this to Pager as totalItems, then select only the
  1237.     * rows you need to display on a page.
  1238.     *
  1239.     * @author Ian Eure <ian@php.net>
  1240.     * 
  1241.     * @access public
  1242.     * 
  1243.     * @param string $sqlkey The name of the SQL SELECT to use from the
  1244.     * $this->sql property array.
  1245.     * 
  1246.     * @param string $filter Ad-hoc SQL snippet to AND with the default
  1247.     * SELECT WHERE clause.
  1248.     * 
  1249.     * @param string $order Ad-hoc SQL snippet to override the default
  1250.     * SELECT ORDER BY clause.
  1251.     * 
  1252.     * @param int $start The row number to start listing from in the
  1253.     * result set.
  1254.     * 
  1255.     * @param int $count The number of rows to list in the result set.
  1256.     * 
  1257.     * @param array $params Parameters to use in placeholder substitutions (if
  1258.     * any).
  1259.     * 
  1260.     * @return mixed An integer number of records from the table, or a
  1261.     * PEAR_Error object.
  1262.     *
  1263.     * @see DB_Table::select()
  1264.     *
  1265.     */
  1266.     
  1267.     function selectCount($sqlkey, $filter = null, $order = null,
  1268.         $start = null, $count = null, $params = array())
  1269.     {
  1270.         // does the SQL SELECT key exist?
  1271.         $tmp = array_keys($this->sql);
  1272.         if (! in_array($sqlkey, $tmp)) {
  1273.             return $this->throwError(
  1274.                 DB_TABLE_ERR_SQL_UNDEF,
  1275.                 "('$sqlkey')"
  1276.             );
  1277.         }
  1278.         
  1279.         // create a SQL key name for this count-query
  1280.         $count_key = '__count_' . $sqlkey;
  1281.         
  1282.         // has a count-query for the SQL key already been created?
  1283.         if (! isset($this->sql[$count_key])) {
  1284.             
  1285.             // we've not asked for a count on this query yet.
  1286.             // get the elements of the query ...
  1287.             $count_sql = $this->sql[$sqlkey];
  1288.             
  1289.             // is a count-field set for the query?
  1290.             if (! isset($count_sql['count']) ||
  1291.                 trim($count_sql['count']) == '') {
  1292.                 $count_sql['count'] = '*';
  1293.             }
  1294.             
  1295.             // replace the SELECT fields with a COUNT() command
  1296.             $count_sql['select'] = "COUNT({$count_sql['count']})";
  1297.             
  1298.             // replace the 'get' key so we only get the one result item
  1299.             $count_sql['get'] = 'one';
  1300.             
  1301.             // create the new count-query in the $sql array
  1302.             $this->sql[$count_key] = $count_sql;
  1303.         }
  1304.         
  1305.         // retrieve the count results
  1306.         return $this->select($count_key, $filter, $order, $start, $count,
  1307.             $params);
  1308.     }
  1309.     
  1310.     
  1311.     /**
  1312.     * 
  1313.     * Changes the $this->db PEAR DB/MDB2 object fetchmode and
  1314.     * fetchmode_object_class.
  1315.     * 
  1316.     * Because DB_Table objects tend to use the same PEAR DB/MDB2 object, it
  1317.     * may sometimes be useful to have one object return results in one
  1318.     * mode, and have another object return results in a different mode. 
  1319.     * This method allows us to switch DB/MDB2 fetch modes on the fly.
  1320.     * 
  1321.     * @access private
  1322.     * 
  1323.     * @param string $new_mode A DB_FETCHMODE_* constant.  If null,
  1324.     * defaults to whatever the DB/MDB2 object is currently using.
  1325.     * 
  1326.     * @param string $new_class The object class to use for results when
  1327.     * the $db object is in DB_FETCHMODE_OBJECT fetch mode.  If null,
  1328.     * defaults to whatever the the DB/MDB2 object is currently using.
  1329.     * 
  1330.     * @return void
  1331.     * 
  1332.     */
  1333.     
  1334.     function _swapModes($new_mode, $new_class)
  1335.     {
  1336.         // get the old (current) mode and class
  1337.         $old_mode = $this->db->fetchmode;
  1338.         if ($this->backend == 'mdb2') {
  1339.             $old_class = $this->db->getOption('fetch_class');
  1340.         } else {
  1341.             $old_class = $this->db->fetchmode_object_class;
  1342.         }
  1343.         
  1344.         // don't need to swap anything if the new modes are both
  1345.         // null or if the old and new modes already match.
  1346.         if ((is_null($new_mode) && is_null($new_class)) ||
  1347.             ($old_mode == $new_mode && $old_class == $new_class)) {
  1348.             return;
  1349.         }
  1350.         
  1351.         // set the default new mode
  1352.         if (is_null($new_mode)) {
  1353.             $new_mode = $old_mode;
  1354.         }
  1355.         
  1356.         // set the default new class
  1357.         if (is_null($new_class)) {
  1358.             $new_class = $old_class;
  1359.         }
  1360.         
  1361.         // swap modes
  1362.         $this->db->setFetchMode($new_mode, $new_class);
  1363.     }
  1364.     
  1365.     
  1366.     /**
  1367.     * 
  1368.     * Builds the SQL command from a specified $this->sql element.
  1369.     * 
  1370.     * @access public
  1371.     * 
  1372.     * @param string $sqlkey The $this->sql key to use as the basis for the
  1373.     * SQL query string.
  1374.     * 
  1375.     * @param string $filter A filter to add to the WHERE clause of the
  1376.     * defined SELECT in $this->sql.
  1377.     * 
  1378.     * @param string $order An ORDER clause to override the defined order
  1379.     * in $this->sql.
  1380.     * 
  1381.     * @param int $start The row number to start listing from in the
  1382.     * result set.
  1383.     * 
  1384.     * @param int $count The number of rows to list in the result set.
  1385.     * 
  1386.     * @return mixed A PEAR_Error on failure, or an SQL command string on
  1387.     * success.
  1388.     * 
  1389.     */
  1390.     
  1391.     function buildSQL($sqlkey, $filter = null, $order = null,
  1392.         $start = null, $count = null)
  1393.     {
  1394.         // does the SQL SELECT key exist?
  1395.         if (is_null($this->sql)) {
  1396.             $this->sql = array();
  1397.         }
  1398.         $tmp = array_keys($this->sql);
  1399.         if (! in_array($sqlkey, $tmp)) {
  1400.             return $this->throwError(
  1401.                 DB_TABLE_ERR_SQL_UNDEF,
  1402.                 "('$sqlkey')"
  1403.             );
  1404.         }
  1405.         
  1406.         // the SQL clause parts and their default values
  1407.         $part = array(
  1408.             'select' => '*',
  1409.             'from'   => $this->table,
  1410.             'join'   => null,
  1411.             'where'  => null,
  1412.             'group'  => null,
  1413.             'having' => null,
  1414.             'order'  => null
  1415.         );
  1416.         
  1417.         // loop through each possible clause
  1418.         foreach ($part as $key => $val) {
  1419.             if (! isset($this->sql[$sqlkey][$key])) {
  1420.                 continue;
  1421.             } else {
  1422.                 $part[$key] = $this->sql[$sqlkey][$key];
  1423.             }
  1424.         }
  1425.         
  1426.         // add the filter to the WHERE part
  1427.         if ($filter) {
  1428.             if (! $part['where']) {
  1429.                 $part['where'] .= $filter;
  1430.             } else {
  1431.                 $part['where'] .= " AND ($filter)";
  1432.             }
  1433.         }
  1434.         
  1435.         // override the ORDER part
  1436.         if ($order) {
  1437.             $part['order'] = $order;
  1438.         }
  1439.         
  1440.         // build up the command string form the parts
  1441.         $cmd = '';
  1442.         foreach ($part as $key => $val) {
  1443.             
  1444.             // if the part value has not been set, skip it
  1445.             if (! $val) {
  1446.                 continue;
  1447.             }
  1448.             
  1449.             switch ($key) {
  1450.             
  1451.             case 'join':
  1452.                 $cmd .= " $val\n";
  1453.                 break;
  1454.                 
  1455.             case 'group':
  1456.             case 'order':
  1457.                 $cmd .= strtoupper($key) . " BY $val\n";
  1458.                 break;
  1459.                 
  1460.             default:
  1461.                 $cmd .= strtoupper($key) . " $val\n";
  1462.                 break;
  1463.             
  1464.             }
  1465.         }
  1466.         
  1467.         // add LIMIT if requested
  1468.         if (! is_null($start) && ! is_null($count)) {
  1469.             if ($this->backend == 'mdb2') {
  1470.                 $this->db->setLimit($count, $start);
  1471.             } else {
  1472.                 $cmd = $this->db->modifyLimitQuery(
  1473.                     $cmd, $start, $count);
  1474.             }
  1475.         }
  1476.         
  1477.         return $cmd;
  1478.     }
  1479.     
  1480.     
  1481.     /**
  1482.     *
  1483.     * Inserts a single table row after validating through validInsert().
  1484.     * 
  1485.     * @access public
  1486.     * 
  1487.     * @param array $data An associative array of key-value pairs where
  1488.     * the key is the column name and the value is the column value.  This
  1489.     * is the data that will be inserted into the table.  Data is checked
  1490.     * against the column data type for validity.
  1491.     * 
  1492.     * @return mixed Void on success, a PEAR_Error object on failure.
  1493.     *
  1494.     * @see validInsert()
  1495.     * 
  1496.     * @see DB::autoExecute()
  1497.     * 
  1498.     * @see MDB2::autoExecute()
  1499.     * 
  1500.     */
  1501.         
  1502.     function insert($data)
  1503.     {
  1504.         // forcibly recast the data elements to their proper types?
  1505.         if ($this->_auto_recast) {
  1506.             $this->recast($data);
  1507.         }
  1508.         
  1509.         // validate the data if auto-validation is turned on
  1510.         if ($this->_valid_insert) {
  1511.             $result = $this->validInsert($data);
  1512.             if (PEAR::isError($result)) {
  1513.                 return $result;
  1514.             }
  1515.         }
  1516.         if ($this->backend == 'mdb2') {
  1517.             $result = $this->db->extended->autoExecute($this->table, $data,
  1518.                 MDB2_AUTOQUERY_INSERT);
  1519.         } else {
  1520.             $result = $this->db->autoExecute($this->table, $data,
  1521.                 DB_AUTOQUERY_INSERT);
  1522.         }
  1523.         return $result;
  1524.     }
  1525.     
  1526.     
  1527.     /**
  1528.     * 
  1529.     * Turns on (or off) automatic validation of inserted data.
  1530.     * 
  1531.     * @access public
  1532.     * 
  1533.     * @param bool $flag True to turn on auto-validation, false to turn it off.
  1534.     * 
  1535.     * @return void
  1536.     * 
  1537.     */
  1538.     
  1539.     function autoValidInsert($flag = true)
  1540.     {
  1541.         if ($flag) {
  1542.             $this->_valid_insert = true;
  1543.         } else {
  1544.             $this->_valid_insert = false;
  1545.         }
  1546.     }
  1547.     
  1548.     
  1549.     /**
  1550.     *
  1551.     * Validates an array for insertion into the table.
  1552.     * 
  1553.     * @access public
  1554.     * 
  1555.     * @param array $data An associative array of key-value pairs where
  1556.     * the key is the column name and the value is the column value.  This
  1557.     * is the data that will be inserted into the table.  Data is checked
  1558.     * against the column data type for validity.
  1559.     * 
  1560.     * @return mixed Boolean true on success, a PEAR_Error object on
  1561.     * failure.
  1562.     *
  1563.     * @see insert()
  1564.     * 
  1565.     */
  1566.         
  1567.     function validInsert(&$data)
  1568.     {
  1569.         // loop through the data, and disallow insertion of unmapped
  1570.         // columns
  1571.         foreach ($data as $col => $val) {
  1572.             if (! isset($this->col[$col])) {
  1573.                 return $this->throwError(
  1574.                     DB_TABLE_ERR_INS_COL_NOMAP,
  1575.                     "('$col')"
  1576.                 );
  1577.             }
  1578.         }
  1579.         
  1580.         // loop through each column mapping, and check the data to be
  1581.         // inserted into it against the column data type. we loop through
  1582.         // column mappings instead of the insert data to make sure that
  1583.         // all necessary columns are being inserted.
  1584.         foreach ($this->col as $col => $val) {
  1585.             
  1586.             // is the value allowed to be null?
  1587.             if (isset($val['require']) &&
  1588.                 $val['require'] == true &&
  1589.                 (! isset($data[$col]) || is_null($data[$col]))) {
  1590.                 return $this->throwError(
  1591.                     DB_TABLE_ERR_INS_COL_REQUIRED,
  1592.                     "'$col'"
  1593.                 );
  1594.             }
  1595.             
  1596.             // does the value to be inserted match the column data type?
  1597.             if (isset($data[$col]) &&
  1598.                 ! $this->isValid($data[$col], $col)) {
  1599.                 return $this->throwError(
  1600.                     DB_TABLE_ERR_INS_DATA_INVALID,
  1601.                     "'$col' ('$data[$col]')"
  1602.                 );
  1603.             }
  1604.         }
  1605.         
  1606.         return true;
  1607.     }
  1608.     
  1609.     
  1610.     /**
  1611.     *
  1612.     * Updates table row(s) matching a custom WHERE clause, after checking
  1613.     * against validUpdate().
  1614.     * 
  1615.     * @access public
  1616.     * 
  1617.     * @param array $data An associative array of key-value pairs where
  1618.     * the key is the column name and the value is the column value.  These
  1619.     * are the columns that will be updated with new values.
  1620.     * 
  1621.     * @param string $where An SQL WHERE clause limiting which records
  1622.     * are to be updated.
  1623.     * 
  1624.     * @return mixed Void on success, a PEAR_Error object on failure.
  1625.     *
  1626.     * @see validUpdate()
  1627.     *
  1628.     * @see DB::autoExecute()
  1629.     * 
  1630.     * @see MDB2::autoExecute()
  1631.     * 
  1632.     */
  1633.     
  1634.     function update($data, $where)
  1635.     {
  1636.         // forcibly recast the data elements to their proper types?
  1637.         if ($this->_auto_recast) {
  1638.             $this->recast($data);
  1639.         }
  1640.         
  1641.         // validate the data if auto-validation is turned on
  1642.         if ($this->_valid_update) {
  1643.             $result = $this->validUpdate($data);
  1644.             if (PEAR::isError($result)) {
  1645.                 return $result;
  1646.             }
  1647.         }
  1648.         
  1649.         if ($this->backend == 'mdb2') {
  1650.             $result = $this->db->extended->autoExecute($this->table, $data,
  1651.                 MDB2_AUTOQUERY_UPDATE, $where);
  1652.         } else {
  1653.             $result = $this->db->autoExecute($this->table, $data,
  1654.                 DB_AUTOQUERY_UPDATE, $where);
  1655.         }
  1656.         return $result;
  1657.  
  1658.     }
  1659.     
  1660.     
  1661.     /**
  1662.     * 
  1663.     * Turns on (or off) automatic validation of updated data.
  1664.     * 
  1665.     * @access public
  1666.     * 
  1667.     * @param bool $flag True to turn on auto-validation, false to turn it off.
  1668.     * 
  1669.     * @return void
  1670.     * 
  1671.     */
  1672.     
  1673.     function autoValidUpdate($flag = true)
  1674.     {
  1675.         if ($flag) {
  1676.             $this->_valid_update = true;
  1677.         } else {
  1678.             $this->_valid_update = false;
  1679.         }
  1680.     }
  1681.     
  1682.     
  1683.     /**
  1684.     *
  1685.     * Validates an array for updating the table.
  1686.     * 
  1687.     * @access public
  1688.     * 
  1689.     * @param array $data An associative array of key-value pairs where
  1690.     * the key is the column name and the value is the column value.  This
  1691.     * is the data that will be inserted into the table.  Data is checked
  1692.     * against the column data type for validity.
  1693.     * 
  1694.     * @return mixed Boolean true on success, a PEAR_Error object on
  1695.     * failure.
  1696.     *
  1697.     * @see update()
  1698.     * 
  1699.     */
  1700.         
  1701.     function validUpdate(&$data)
  1702.     {
  1703.         // loop through each data element, and check the
  1704.         // data to be updated against the column data type.
  1705.         foreach ($data as $col => $val) {
  1706.             
  1707.             // does the column exist?
  1708.             if (! isset($this->col[$col])) {
  1709.                 return $this->throwError(
  1710.                     DB_TABLE_ERR_UPD_COL_NOMAP,
  1711.                     "('$col')"
  1712.                 );
  1713.             }
  1714.             
  1715.             // the column definition
  1716.             $defn = $this->col[$col];
  1717.             
  1718.             // is it allowed to be null?
  1719.             if (isset($defn['require']) &&
  1720.                 $defn['require'] == true &&
  1721.                 isset($data[$col]) &&
  1722.                 is_null($data[$col])) {
  1723.                 return $this->throwError(
  1724.                     DB_TABLE_ERR_UPD_COL_REQUIRED,
  1725.                     $col
  1726.                 );
  1727.             }
  1728.             
  1729.             // does the value to be inserted match the column data type?
  1730.             if (! $this->isValid($data[$col], $col)) {
  1731.                 return $this->throwError(
  1732.                     DB_TABLE_ERR_UPD_DATA_INVALID,
  1733.                     "$col ('$data[$col]')"
  1734.                 );
  1735.             }
  1736.         }
  1737.         
  1738.         return true;
  1739.     }
  1740.     
  1741.     
  1742.     /**
  1743.     *
  1744.     * Deletes table rows matching a custom WHERE clause.
  1745.     * 
  1746.     * @access public
  1747.     * 
  1748.     * @param string $where The WHERE clause for the delete command.
  1749.     *
  1750.     * @return mixed Void on success or a PEAR_Error object on failure.
  1751.     *
  1752.     * @see DB::query()
  1753.     * 
  1754.     * @see MDB2::exec()
  1755.     * 
  1756.     */
  1757.     
  1758.     function delete($where)
  1759.     {
  1760.         if ($this->backend == 'mdb2') {
  1761.             $result = $this->db->exec("DELETE FROM $this->table WHERE $where");
  1762.         } else {
  1763.             $result = $this->db->query("DELETE FROM $this->table WHERE $where");
  1764.         }
  1765.         return $result;
  1766.     }
  1767.     
  1768.     
  1769.     /**
  1770.     *
  1771.     * Generates a sequence value; sequence name defaults to the table name.
  1772.     * 
  1773.     * @access public
  1774.     * 
  1775.     * @param string $seq_name The sequence name; defaults to table_id.
  1776.     * 
  1777.     * @return integer The next value in the sequence.
  1778.     *
  1779.     * @see DB::nextID()
  1780.     * 
  1781.     * @see MDB2::nextID()
  1782.     *
  1783.     */
  1784.     
  1785.     function nextID($seq_name = null)
  1786.     {
  1787.         if (is_null($seq_name)) {
  1788.             $seq_name = "{$this->table}";
  1789.         } else {
  1790.             $seq_name = "{$this->table}_{$seq_name}";
  1791.         }
  1792.         
  1793.         // the maximum length is 30, but PEAR DB/MDB2 will add "_seq" to the
  1794.         // name, so the max length here is less 4 chars. we have to
  1795.         // check here because the sequence will be created automatically
  1796.         // by PEAR DB/MDB2, which will not check for length on its own.
  1797.         if (strlen($seq_name) > 26) {
  1798.             return DB_Table::throwError(
  1799.                 DB_TABLE_ERR_SEQ_STRLEN,
  1800.                 " ('$seq_name')"
  1801.             );
  1802.             
  1803.         }
  1804.         return $this->db->nextId($seq_name);
  1805.     }
  1806.     
  1807.     
  1808.     /**
  1809.     * 
  1810.     * Escapes and enquotes a value for use in an SQL query.
  1811.     * 
  1812.     * Helps makes user input safe against SQL injection attack.
  1813.     * 
  1814.     * @access public
  1815.     * 
  1816.     * @return string The value with quotes escaped, and inside single quotes.
  1817.     * 
  1818.     * @see DB_Common::quoteSmart()
  1819.     * 
  1820.     * @see MDB2::quote()
  1821.     * 
  1822.     */
  1823.     
  1824.     function quote($val)
  1825.     {
  1826.         if ($this->backend == 'mdb2') {
  1827.             $val = $this->db->quote($val);
  1828.         } else {
  1829.             $val = $this->db->quoteSmart($val);
  1830.         }
  1831.         return $val;
  1832.     }
  1833.     
  1834.     
  1835.     /**
  1836.     * 
  1837.     * Returns a blank row array based on the column map.
  1838.     * 
  1839.     * The array keys are the column names, and all values are set to null.
  1840.     * 
  1841.     * @access public
  1842.     * 
  1843.     * @return array An associative array where the key is column name
  1844.     * and the value is null.
  1845.     * 
  1846.     */
  1847.     
  1848.     function getBlankRow()
  1849.     {
  1850.         $row = array();
  1851.         
  1852.         foreach ($this->col as $key => $val) {
  1853.             $row[$key] = null;
  1854.         }
  1855.         
  1856.         $this->recast($row);
  1857.         
  1858.         return $row;
  1859.     }
  1860.     
  1861.     
  1862.     /**
  1863.     * 
  1864.     * Turns on (or off) automatic recasting of insert and update data.
  1865.     * 
  1866.     * @access public
  1867.     * 
  1868.     * @param bool $flag True to autmatically recast insert and update data,
  1869.     * false to not do so.
  1870.     *
  1871.     * @return void
  1872.     * 
  1873.     */
  1874.     
  1875.     function autoRecast($flag = true)
  1876.     {
  1877.         if ($flag) {
  1878.             $this->_auto_recast = true;
  1879.         } else {
  1880.             $this->_auto_recast = false;
  1881.         }
  1882.     }
  1883.     
  1884.     
  1885.     /**
  1886.     * 
  1887.     * Forces array elements to the proper types for their columns.
  1888.     * 
  1889.     * This will not valiate the data, and will forcibly change the data
  1890.     * to match the recast-type.
  1891.     * 
  1892.     * The date, time, and timestamp recasting has special logic for
  1893.     * arrays coming from an HTML_QuickForm object so that the arrays
  1894.     * are converted into properly-formatted strings.
  1895.     * 
  1896.     * @todo If a column key holds an array of values (say from a multiple
  1897.     * select) then this method will not work properly; it will recast the
  1898.     * value to the string 'Array'.  Is this bad?
  1899.     * 
  1900.     * @access public
  1901.     * 
  1902.     * @param array &$data The data array to re-cast.
  1903.     *
  1904.     * @return void
  1905.     * 
  1906.     */
  1907.     
  1908.     function recast(&$data)
  1909.     {
  1910.         $keys = array_keys($data);
  1911.         
  1912.         $null_if_blank = array('date', 'time', 'timestamp', 'smallint',
  1913.             'integer', 'bigint', 'decimal', 'single', 'double');
  1914.         
  1915.         foreach ($keys as $key) {
  1916.         
  1917.             if (! isset($this->col[$key])) {
  1918.                 continue;
  1919.             }
  1920.             
  1921.             unset($val);
  1922.             $val =& $data[$key];
  1923.             
  1924.             // convert blanks to null for non-character field types
  1925.             $convert = in_array($this->col[$key]['type'], $null_if_blank);
  1926.             if (is_array($val)) {  // if one of the given array values is
  1927.                                    // empty, null will be the new value if
  1928.                                    // the field is not required
  1929.                 $tmp_val = implode('', $val);
  1930.                 foreach ($val as $array_val) {
  1931.                     if (trim((string) $array_val) == '') {
  1932.                         $tmp_val = '';
  1933.                         break;
  1934.                     }
  1935.                 }
  1936.             } else {
  1937.                 $tmp_val = $val;
  1938.             }
  1939.             if ($convert && trim((string) $tmp_val) == '' && (
  1940.                 !isset($this->col[$key]['require']) ||
  1941.                 $this->col[$key]['require'] === false
  1942.               )
  1943.             ) {
  1944.                 $val = null;
  1945.             }
  1946.             
  1947.             // skip explicit NULL values
  1948.             if (is_null($val)) {
  1949.                 continue;
  1950.             }
  1951.             
  1952.             // otherwise, recast to the column type
  1953.             switch ($this->col[$key]['type']) {
  1954.             
  1955.             case 'boolean':
  1956.                 $val = ($val) ? 1 : 0;
  1957.                 break;
  1958.                 
  1959.             case 'char':
  1960.             case 'varchar':
  1961.             case 'clob':
  1962.                 settype($val, 'string');
  1963.                 break;
  1964.                 
  1965.             case 'date':
  1966.  
  1967.                 // smart handling of non-standard (i.e. Y-m-d) date formats,
  1968.                 // this allows to use two-digit years (y) and short (M) or
  1969.                 // long (F) names of months without having to recast the
  1970.                 // date value yourself
  1971.                 if (is_array($val)) {
  1972.                     if (isset($val['y'])) {
  1973.                         $val['Y'] = $val['y'];
  1974.                     }
  1975.                     if (isset($val['F'])) {
  1976.                         $val['m'] = $val['F'];
  1977.                     }
  1978.                     if (isset($val['M'])) {
  1979.                         $val['m'] = $val['M'];
  1980.                     }
  1981.                 }
  1982.  
  1983.                 if (is_array($val) &&
  1984.                     isset($val['Y']) &&
  1985.                     isset($val['m']) &&
  1986.                     isset($val['d'])) {
  1987.                     
  1988.                     // the date is in HTML_QuickForm format,
  1989.                     // convert into a string
  1990.                     $y = (strlen($val['Y']) < 4)
  1991.                         ? str_pad($val['Y'], 4, '0', STR_PAD_LEFT)
  1992.                         : $val['Y'];
  1993.                     
  1994.                     $m = (strlen($val['m']) < 2)
  1995.                         ? '0'.$val['m'] : $val['m'];
  1996.                         
  1997.                     $d = (strlen($val['d']) < 2)
  1998.                         ? '0'.$val['d'] : $val['d'];
  1999.                         
  2000.                     $val = "$y-$m-$d";
  2001.                     
  2002.                 } else {
  2003.                 
  2004.                     // convert using the Date class
  2005.                     $tmp =& new DB_Table_Date($val);
  2006.                     $val = $tmp->format('%Y-%m-%d');
  2007.                     
  2008.                 }
  2009.                 
  2010.                 break;
  2011.             
  2012.             case 'time':
  2013.             
  2014.                 if (is_array($val) &&
  2015.                     isset($val['H']) &&
  2016.                     isset($val['i']) &&
  2017.                     isset($val['s'])) {
  2018.                     
  2019.                     // the time is in HTML_QuickForm format,
  2020.                     // convert into a string
  2021.                     $h = (strlen($val['H']) < 2)
  2022.                         ? '0' . $val['H'] : $val['H'];
  2023.                     
  2024.                     $i = (strlen($val['i']) < 2)
  2025.                         ? '0' . $val['i'] : $val['i'];
  2026.                         
  2027.                     $s = (strlen($val['s']) < 2)
  2028.                         ? '0' . $val['s'] : $val['s'];
  2029.                         
  2030.                         
  2031.                     $val = "$h:$i:$s";
  2032.                     
  2033.                 } else {
  2034.                     // date does not matter in this case, so
  2035.                     // pre 1970 and post 2040 are not an issue.
  2036.                     $tmp = strtotime(date('Y-m-d') . " $val");
  2037.                     $val = date('H:i:s', $tmp);
  2038.                 }
  2039.                 
  2040.                 break;
  2041.                 
  2042.             case 'timestamp':
  2043.  
  2044.                 // smart handling of non-standard (i.e. Y-m-d) date formats,
  2045.                 // this allows to use two-digit years (y) and short (M) or
  2046.                 // long (F) names of months without having to recast the
  2047.                 // date value yourself
  2048.                 if (is_array($val)) {
  2049.                     if (isset($val['y'])) {
  2050.                         $val['Y'] = $val['y'];
  2051.                     }
  2052.                     if (isset($val['F'])) {
  2053.                         $val['m'] = $val['F'];
  2054.                     }
  2055.                     if (isset($val['M'])) {
  2056.                         $val['m'] = $val['M'];
  2057.                     }
  2058.                 }
  2059.  
  2060.                 if (is_array($val) &&
  2061.                     isset($val['Y']) &&
  2062.                     isset($val['m']) &&
  2063.                     isset($val['d']) &&
  2064.                     isset($val['H']) &&
  2065.                     isset($val['i']) &&
  2066.                     isset($val['s'])) {
  2067.                     
  2068.                     // timestamp is in HTML_QuickForm format,
  2069.                     // convert each element to a string. pad
  2070.                     // with zeroes as needed.
  2071.                 
  2072.                     $y = (strlen($val['Y']) < 4)
  2073.                         ? str_pad($val['Y'], 4, '0', STR_PAD_LEFT)
  2074.                         : $val['Y'];
  2075.                     
  2076.                     $m = (strlen($val['m']) < 2)
  2077.                         ? '0'.$val['m'] : $val['m'];
  2078.                         
  2079.                     $d = (strlen($val['d']) < 2)
  2080.                         ? '0'.$val['d'] : $val['d'];
  2081.                         
  2082.                     $h = (strlen($val['H']) < 2)
  2083.                         ? '0' . $val['H'] : $val['H'];
  2084.                     
  2085.                     $i = (strlen($val['i']) < 2)
  2086.                         ? '0' . $val['i'] : $val['i'];
  2087.                         
  2088.                     $s = (strlen($val['s']) < 2)
  2089.                         ? '0' . $val['s'] : $val['s'];
  2090.                         
  2091.                     $val = "$y-$m-$d $h:$i:$s";
  2092.                     
  2093.                 } else {
  2094.                     // convert using the Date class
  2095.                     $tmp =& new DB_Table_Date($val);
  2096.                     $val = $tmp->format('%Y-%m-%d %H:%M:%S');
  2097.                 }
  2098.                 
  2099.                 break;
  2100.             
  2101.             case 'smallint':
  2102.             case 'integer':
  2103.             case 'bigint':
  2104.                 settype($val, 'integer');
  2105.                 break;
  2106.             
  2107.             case 'decimal':
  2108.             case 'single':
  2109.             case 'double':
  2110.                 settype($val, 'float');
  2111.                 break;
  2112.  
  2113.             }
  2114.         }
  2115.     }
  2116.     
  2117.     
  2118.     /**
  2119.     * 
  2120.     * Creates the table based on $this->col and $this->idx.
  2121.     * 
  2122.     * @access public
  2123.     * 
  2124.     * @param mixed $flag The automatic table creation mode to pursue:
  2125.     * - 'safe' to create the table only if it does not exist
  2126.     * - 'drop' to drop any existing table with the same name and re-create it
  2127.     * 
  2128.     * @return mixed Boolean false if there was no need to create the
  2129.     * table, boolean true if the attempt succeeded, or a PEAR_Error if
  2130.     * the attempt failed.
  2131.     * 
  2132.     * @see DB_Table_Manager::create()
  2133.     * 
  2134.     */
  2135.     
  2136.     function create($flag)
  2137.     {
  2138.  
  2139.         // are we OK to create the table?
  2140.         $ok = false;
  2141.         
  2142.         // check the create-flag
  2143.         switch ($flag) {
  2144.  
  2145.             case 'drop':
  2146.                 // drop only if table exists
  2147.                 $table_exists = DB_Table_Manager::tableExists($this->db,
  2148.                                                               $this->table);
  2149.                 if (PEAR::isError($table_exists)) {
  2150.                     return $table_exists;
  2151.                 }
  2152.                 if ($table_exists) {
  2153.                     // forcibly drop an existing table
  2154.                     if ($this->backend == 'mdb2') {
  2155.                         $this->db->manager->dropTable($this->table);
  2156.                     } else {
  2157.                         $this->db->query("DROP TABLE {$this->table}");
  2158.                     }
  2159.                 }
  2160.                 $ok = true;
  2161.                 break;
  2162.  
  2163.             case 'safe':
  2164.                 // create only if table does not exist
  2165.                 $table_exists = DB_Table_Manager::tableExists($this->db,
  2166.                                                               $this->table);
  2167.                 if (PEAR::isError($table_exists)) {
  2168.                     return $table_exists;
  2169.                 }
  2170.                 // ok to create only if table does not exist
  2171.                 $ok = !$table_exists;
  2172.                 break;
  2173.  
  2174.         }
  2175.  
  2176.         // are we going to create the table?
  2177.         if (! $ok) {
  2178.             return false;
  2179.         }
  2180.  
  2181.         return DB_Table_Manager::create(
  2182.             $this->db, $this->table, $this->col, $this->idx
  2183.         );
  2184.     }
  2185.     
  2186.     
  2187.     /**
  2188.     * 
  2189.     * Alters the table based on $this->col and $this->idx.
  2190.     * 
  2191.     * @access public
  2192.     * 
  2193.     * @return mixed Boolean true if altering was successful or a PEAR_Error on
  2194.     * failure.
  2195.     *
  2196.     * @see DB_Table_Manager::alter()
  2197.     * 
  2198.     */
  2199.     
  2200.     function alter()
  2201.     {
  2202.         $create = false;
  2203.         
  2204.         // alter the table columns and indexes if the table exists
  2205.         $table_exists = DB_Table_Manager::tableExists($this->db,
  2206.                                                       $this->table);
  2207.         if (PEAR::isError($table_exists)) {
  2208.             return $table_exists;
  2209.         }
  2210.         if (!$table_exists) {
  2211.             // table does not exist => just create the table, there is
  2212.             // nothing that could be altered
  2213.             $create = true;
  2214.         }
  2215.  
  2216.         if ($create) {
  2217.             return DB_Table_Manager::create(
  2218.                 $this->db, $this->table, $this->col, $this->idx
  2219.             );
  2220.         }
  2221.  
  2222.         return DB_Table_Manager::alter(
  2223.             $this->db, $this->table, $this->col, $this->idx
  2224.         );
  2225.     }
  2226.     
  2227.     
  2228.     /**
  2229.     * 
  2230.     * Verifies the table based on $this->col and $this->idx.
  2231.     * 
  2232.     * @access public
  2233.     * 
  2234.     * @return mixed Boolean true if the verification was successful, and a
  2235.     * PEAR_Error if verification failed.
  2236.     *
  2237.     * @see DB_Table_Manager::verify()
  2238.     * 
  2239.     */
  2240.     
  2241.     function verify()
  2242.     {
  2243.         return DB_Table_Manager::verify(
  2244.             $this->db, $this->table, $this->col, $this->idx
  2245.         );
  2246.     }
  2247.     
  2248.     
  2249.     /**
  2250.     * 
  2251.     * Checks if a value validates against the DB_Table data type for a
  2252.     * given column. This only checks that it matches the data type; it
  2253.     * does not do extended validation.
  2254.     * 
  2255.     * @access public
  2256.     * 
  2257.     * @param array $val A value to check against the column's DB_Table
  2258.     * data type.
  2259.     * 
  2260.     * @param array $col A column name from $this->col.
  2261.     * 
  2262.     * @return boolean True if the value validates (matches the
  2263.     * data type), false if not.
  2264.     * 
  2265.     * @see DB_Table_Valid
  2266.     * 
  2267.     */
  2268.     
  2269.     function isValid($val, $col)
  2270.     {
  2271.         // is the value null?
  2272.         if (is_null($val)) {
  2273.             // is the column required?
  2274.             if ($this->isRequired($col)) {
  2275.                 // yes, so not valid
  2276.                 return false;
  2277.             } else {
  2278.                 // not required, so it's valid
  2279.                 return true;
  2280.             }
  2281.         }
  2282.         
  2283.         // make sure we have the validation class
  2284.         include_once 'DB/Table/Valid.php';
  2285.         
  2286.         // validate values per the column type.  we use sqlite
  2287.         // as the single authentic list of allowed column types,
  2288.         // regardless of the actual rdbms being used.
  2289.         $map = array_keys($GLOBALS['_DB_TABLE']['type']['sqlite']);
  2290.         
  2291.         // is the column type on the map?
  2292.         if (! in_array($this->col[$col]['type'], $map)) {
  2293.             return $this->throwError(
  2294.                 DB_TABLE_ERR_VALIDATE_TYPE,
  2295.                 "'$col' ('{$this->col[$col]['type']}')"
  2296.             );
  2297.         }
  2298.         
  2299.         // validate for the type
  2300.         switch ($this->col[$col]['type']) {
  2301.         
  2302.         case 'char':
  2303.         case 'varchar':
  2304.             $result = DB_Table_Valid::isChar(
  2305.                 $val,
  2306.                 $this->col[$col]['size']
  2307.             );
  2308.             break;
  2309.         
  2310.         case 'decimal':
  2311.             $result = DB_Table_Valid::isDecimal(
  2312.                 $val,
  2313.                 $this->col[$col]['size'],
  2314.                 $this->col[$col]['scope']
  2315.             );
  2316.             break;
  2317.             
  2318.         default:
  2319.             $result = call_user_func(
  2320.                 array(
  2321.                     'DB_Table_Valid',
  2322.                     'is' . ucwords($this->col[$col]['type'])
  2323.                 ),
  2324.                 $val
  2325.             );
  2326.             break;
  2327.  
  2328.         }
  2329.         
  2330.         // have we passed the check so far, and should we
  2331.         // also check for allowed values?
  2332.         if ($result && isset($this->col[$col]['qf_vals'])) {
  2333.             $keys = array_keys($this->col[$col]['qf_vals']);
  2334.             
  2335.             $result = in_array(
  2336.                 $val,
  2337.                 array_keys($this->col[$col]['qf_vals'])
  2338.             );
  2339.         }
  2340.         
  2341.         return $result;
  2342.     }
  2343.     
  2344.     
  2345.     /**
  2346.     * 
  2347.     * Is a specific column required to be set and non-null?
  2348.     * 
  2349.     * @access public
  2350.     * 
  2351.     * @param mixed $column The column to check against.
  2352.     * 
  2353.     * @return boolean True if required, false if not.
  2354.     * 
  2355.     */
  2356.     
  2357.     function isRequired($column)
  2358.     {
  2359.         if (isset($this->col[$column]['require']) &&
  2360.             $this->col[$column]['require'] == true) {
  2361.             return true;
  2362.         } else {
  2363.             return false;
  2364.         }
  2365.     }
  2366.     
  2367.     
  2368.     /**
  2369.     * 
  2370.     * Creates and returns a QuickForm object based on table columns.
  2371.     *
  2372.     * @access public
  2373.     *
  2374.     * @param array $columns A sequential array of column names to use in
  2375.     * the form; if null, uses all columns.
  2376.     *
  2377.     * @param string $array_name By default, the form will use the names
  2378.     * of the columns as the names of the form elements.  If you pass
  2379.     * $array_name, the column names will become keys in an array named
  2380.     * for this parameter.
  2381.     * 
  2382.     * @param array $args An associative array of optional arguments to
  2383.     * pass to the QuickForm object.  The keys are...
  2384.     *
  2385.     * 'formName' : String, name of the form; defaults to the name of this
  2386.     * table.
  2387.     * 
  2388.     * 'method' : String, form method; defaults to 'post'.
  2389.     * 
  2390.     * 'action' : String, form action; defaults to
  2391.     * $_SERVER['REQUEST_URI'].
  2392.     * 
  2393.     * 'target' : String, form target target; defaults to '_self'
  2394.     * 
  2395.     * 'attributes' : Associative array, extra attributes for <form>
  2396.     * tag; the key is the attribute name and the value is attribute
  2397.     * value.
  2398.     * 
  2399.     * 'trackSubmit' : Boolean, whether to track if the form was
  2400.     * submitted by adding a special hidden field
  2401.     * 
  2402.     * @param string $clientValidate By default, validation will match
  2403.     * the 'qf_client' value from the column definition.  However,
  2404.     * if you set $clientValidate to true or false, this will
  2405.     * override the value from the column definition.
  2406.     *
  2407.     * @param array $formFilters An array with filter function names or
  2408.     * callbacks that will be applied to all form elements.
  2409.     *
  2410.     * @return object HTML_QuickForm
  2411.     * 
  2412.     * @see HTML_QuickForm
  2413.     * 
  2414.     * @see DB_Table_QuickForm
  2415.     * 
  2416.     */
  2417.     
  2418.     function &getForm($columns = null, $array_name = null, $args = array(),
  2419.         $clientValidate = null, $formFilters = null)
  2420.     {
  2421.         include_once 'DB/Table/QuickForm.php';
  2422.         $coldefs = $this->_getFormColDefs($columns);
  2423.         $form =& DB_Table_QuickForm::getForm($coldefs, $array_name, $args,
  2424.             $clientValidate, $formFilters);
  2425.         return $form;
  2426.     }
  2427.     
  2428.     
  2429.     /**
  2430.     * 
  2431.     * Adds elements and rules to a pre-existing HTML_QuickForm object.
  2432.     * 
  2433.     * @access public
  2434.     * 
  2435.     * @param object &$form An HTML_QuickForm object.
  2436.     * 
  2437.     * @param array $columns A sequential array of column names to use in
  2438.     * the form; if null, uses all columns.
  2439.     *
  2440.     * @param string $array_name By default, the form will use the names
  2441.     * of the columns as the names of the form elements.  If you pass
  2442.     * $array_name, the column names will become keys in an array named
  2443.     * for this parameter.
  2444.     * 
  2445.     * @return void
  2446.     * 
  2447.     * @see HTML_QuickForm
  2448.     * 
  2449.     * @see DB_Table_QuickForm
  2450.     * 
  2451.     */
  2452.     
  2453.     function addFormElements(&$form, $columns = null, $array_name = null,
  2454.         $clientValidate = null)
  2455.     {
  2456.         include_once 'DB/Table/QuickForm.php';
  2457.         $coldefs = $this->_getFormColDefs($columns);
  2458.         DB_Table_QuickForm::addElements($form, $coldefs, $array_name);
  2459.         DB_Table_QuickForm::addRules($form, $coldefs, $array_name,
  2460.             $clientValidate);
  2461.     }
  2462.  
  2463.  
  2464.     /**
  2465.     * 
  2466.     * Adds static form elements like 'header', 'static', 'submit' or 'reset' to
  2467.     * a pre-existing HTML_QuickForm object. The form elements needs to be
  2468.     * defined in a property called $frm.
  2469.     * 
  2470.     * @access public
  2471.     * 
  2472.     * @param object &$form An HTML_QuickForm object.
  2473.     * 
  2474.     * @return void
  2475.     * 
  2476.     * @see HTML_QuickForm
  2477.     * 
  2478.     * @see DB_Table_QuickForm
  2479.     * 
  2480.     */
  2481.  
  2482.     function addStaticFormElements(&$form)
  2483.     {
  2484.         include_once 'DB/Table/QuickForm.php';
  2485.         DB_Table_QuickForm::addStaticElements($form, $this->frm);
  2486.     }
  2487.  
  2488.  
  2489.     /**
  2490.     * 
  2491.     * Creates and returns an array of QuickForm elements based on an
  2492.     * array of DB_Table column names.
  2493.     * 
  2494.     * @access public
  2495.     * 
  2496.     * @param array $columns A sequential array of column names to use in
  2497.     * the form; if null, uses all columns.
  2498.     * 
  2499.     * @param string $array_name By default, the form will use the names
  2500.     * of the columns as the names of the form elements.  If you pass
  2501.     * $array_name, the column names will become keys in an array named
  2502.     * for this parameter.
  2503.     * 
  2504.     * @return array An array of HTML_QuickForm_Element objects.
  2505.     * 
  2506.     * @see HTML_QuickForm
  2507.     * 
  2508.     * @see DB_Table_QuickForm
  2509.     * 
  2510.     */
  2511.     
  2512.     function &getFormGroup($columns = null, $array_name = null)
  2513.     {
  2514.         include_once 'DB/Table/QuickForm.php';
  2515.         $coldefs = $this->_getFormColDefs($columns);
  2516.         $group =& DB_Table_QuickForm::getGroup($coldefs, $array_name);
  2517.         return $group;
  2518.     }
  2519.     
  2520.     
  2521.     /**
  2522.     * 
  2523.     * Creates and returns a single QuickForm element based on a DB_Table
  2524.     * column name.
  2525.     * 
  2526.     * @access public
  2527.     * 
  2528.     * @param string $column A DB_Table column name.
  2529.     * 
  2530.     * @param string $elemname The name to use for the generated QuickForm
  2531.     * element.
  2532.     * 
  2533.     * @return object HTML_QuickForm_Element
  2534.     * 
  2535.     * @see HTML_QuickForm
  2536.     * 
  2537.     * @see DB_Table_QuickForm
  2538.     * 
  2539.     */
  2540.     
  2541.     function &getFormElement($column, $elemname)
  2542.     {
  2543.         include_once 'DB/Table/QuickForm.php';
  2544.         $coldef = $this->_getFormColDefs($column);
  2545.         DB_Table_QuickForm::fixColDef($coldef[$column], $elemname);
  2546.         $element =& DB_Table_QuickForm::getElement($coldef[$column],
  2547.             $elemname);
  2548.         return $element;
  2549.     }
  2550.  
  2551.     /**
  2552.     * 
  2553.     * Creates and returns an array of QuickForm elements based on a DB_Table
  2554.     * column name.
  2555.     * 
  2556.     * @author Ian Eure <ieure@php.net>
  2557.     * 
  2558.     * @access public
  2559.     * 
  2560.     * @param array $cols Array of DB_Table column names
  2561.     * 
  2562.     * @param string $array_name The name to use for the generated QuickForm
  2563.     * elements.
  2564.     * 
  2565.     * @return object HTML_QuickForm_Element
  2566.     * 
  2567.     * @see HTML_QuickForm
  2568.     * 
  2569.     * @see DB_Table_QuickForm
  2570.     * 
  2571.     */
  2572.     function &getFormElements($cols, $array_name = null)
  2573.     {
  2574.         include_once 'DB/Table/QuickForm.php';
  2575.         $elements =& DB_Table_QuickForm::getElements($cols, $array_name);
  2576.         return $elements;
  2577.     }
  2578.     
  2579.     
  2580.     /**
  2581.     * 
  2582.     * Creates a column definition array suitable for DB_Table_QuickForm.
  2583.     * 
  2584.     * @access public
  2585.     * 
  2586.     * @param string|array $column_set A string column name, a sequential
  2587.     * array of columns names, or an associative array where the key is a
  2588.     * column name and the value is the default value for the generated
  2589.     * form element.  If null, uses all columns for this class.
  2590.     * 
  2591.     * @return array An array of columne defintions suitable for passing
  2592.     * to DB_Table_QuickForm.
  2593.     * 
  2594.     */
  2595.     
  2596.     function _getFormColDefs($column_set = null)
  2597.     {
  2598.         if (is_null($column_set)) {
  2599.             // no columns or columns+values; just return the $this->col
  2600.             // array.
  2601.             return $this->getColumns($column_set);
  2602.         }
  2603.         
  2604.         // check to see if the keys are sequential integers.  if so,
  2605.         // the $column_set is just a list of columns.
  2606.         settype($column_set, 'array');
  2607.         $keys = array_keys($column_set);
  2608.         $all_integer = true;
  2609.         foreach ($keys as $val) {
  2610.             if (! is_integer($val)) {
  2611.                 $all_integer = false;
  2612.                 break;
  2613.             }
  2614.         }
  2615.         
  2616.         if ($all_integer) {
  2617.         
  2618.             // the column_set is just a list of columns; get back the $this->col
  2619.             // array elements matching this list.
  2620.             $coldefs = $this->getColumns($column_set);
  2621.             
  2622.         } else {
  2623.             
  2624.             // the columns_set is an associative array where the key is a
  2625.             // column name and the value is the form element value.
  2626.             $coldefs = $this->getColumns($keys);
  2627.             foreach ($coldefs as $key => $val) {
  2628.                 $coldefs[$key]['qf_setvalue'] = $column_set[$key];
  2629.             }
  2630.             
  2631.         }
  2632.         
  2633.         return $coldefs;
  2634.     }
  2635.  
  2636. }
  2637. ?>
  2638.